package org.nvframe.manager;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.newdawn.slick.Input;
import org.newdawn.slick.util.ResourceLoader;
import org.nvframe.exception.NVFrameException;
import org.nvframe.exception.NVFrameRuntimeException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 
 * @author Nik Van Looy
 */
public class InputManager {

	private final static InputManager _instance = new InputManager();

	public static InputManager getInstance() {
		return _instance;
	}
	
	private static HashMap<String, Integer> keyMap;

	private InputManager() {
		keyMap = new HashMap<String, Integer>();
	}
	
	/**
	 * Adds a keybinding
	 * 
	 * @param inputName The identifier of the binding
	 * @param keyCode The keycode
	 */
	public void addInput(String inputName, int keyCode) {
		keyMap.put(inputName, keyCode);
	}
	
	/**
	 * Get the Slick keycode for a given inputname
	 * 
	 * @param inputName The inputname to get the code from
	 * @return The keycode (Slick Input)
	 */
	public int getInput(String inputName) {
		if(!keyMap.containsKey(inputName))
			throw new NVFrameRuntimeException("InputManager: no keybinding found with id: " + inputName);
		
		return keyMap.get(inputName);
	}
	
	/**
	 * Prints all the keybindings (used for debugging)
	 */
	public void showAllBindings() {	
		
		for(String inputName : keyMap.keySet())
			System.out.println("keybinding - " + inputName + " | " + getSlickKeyCode(keyMap.get(inputName)));
	}
	
	/**
	 * Gets the SLick keyname from with a given keycode (only for debugging!!!)
	 * eg: 0x02 = KEY_1
	 * TODO: improve this code by NOT using reflective api :p
	 * 
	 * @param keycode
	 * @return The keyname
	 */
	private String getSlickKeyCode(int keycode) {
		try {
			Field[] fields = Input.class.getFields();
			
			for(Field field : fields) {
				if(field.getName().startsWith("KEY") || field.getName().startsWith("MOUSE")) {
					int keyc = field.getInt(null);
					
					if(keyc == keycode) 
						return field.getName();
				}
			}
		} catch (Exception e) {
			throw new NVFrameRuntimeException("Slick inputkey not found", e);
		}
		
		throw new NVFrameRuntimeException("keyname not found for code: " + keycode);
	}
	
	/**
	 * Gets the SLick keycode from with a given name
	 * eg: KEY_1 = 0x02
	 * 
	 * @param name The keyname eg: KEY_1
	 * @return The keycode
	 */
	private int getSlickKeyCode(String name) {
		try {
			Field inputProp = Input.class.getField(name);
			return (Integer) inputProp.get(null);
		} catch (Exception e) {
			throw new NVFrameRuntimeException("Slick inputkey not found", e);
		}
	}
	
	/**
	 * 
	 * @param location
	 * @throws NVFrameException
	 */
	public void loadInputKeys(String location) throws NVFrameException {
		InputStream inRes = ResourceLoader.getResourceAsStream(location);
		
		try {
			loadInputKeys(inRes);
		} catch (Exception e) {
			throw new NVFrameException("could not load resource: " + location, e);
		}
	}
	
	/**
	 * Load the configured keybindings from an XML resource
	 * 
	 * @param is The XML resource as InputStream
	 * @throws NVFrameException
	 */
	public void loadInputKeys(InputStream is) throws NVFrameException {
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = null;
		
		try {
			docBuilder = docBuilderFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new NVFrameException("Could not load keymap xmlfile", e);
		}
		
		Document doc = null;
		
		try {
			doc = docBuilder.parse(is);
		} catch (Exception e) {
			throw new NVFrameException("Could not load keymap xmlfile", e);
		}

		doc.getDocumentElement().normalize();

		// load the root tag
		NodeList listInputs = doc.getElementsByTagName("input");

		int totalInputs = listInputs.getLength();

		for (int inputId = 0; inputId < totalInputs; inputId++) {

			Node inputNode = listInputs.item(inputId);

			if (inputNode.getNodeType() == Node.ELEMENT_NODE) {
				Element resourceElement = (Element) inputNode;

				String inputName = resourceElement.getAttribute("id");
				String inputKey = resourceElement.getTextContent();
				
				addInput(inputName, getSlickKeyCode(inputKey));
				
			}
		}
	}

}
